﻿/*
 * Copyright (c) 2011
 * Thomas Rozanski, Geoffrey Wright, Zachary Lynn, Jesse Natalie
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THOMAS ROZANSKI, GEOFFREY WRIGHT,
 * ZACHARY LYNN, OR JESSE NATALIE BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using Kinected;

#if RELEASE_TIMER
using System.Diagnostics;
#endif

namespace Pinball
{
    /// <summary>Runs the pinball simulation of the balls and bumpers.</summary>
    class Pinball
    {
        // Balls and bumpers
        private List<Ball> balls;
        private Dictionary<int, Bumper> bumpers;

        private int maxBalls;

        // Texture for bumpers and balls
        private Texture2D ballTexture;
        private Texture2D bumperTexture;

        // Texture widths
        private float ballWidth, bumperWidth;

        // Bounding game rectangle
        private Rectangle boardBounds;

        private Random random;

        private SoundEffect bumperHit;
        private double lastHit;

        /// <param name="maxBalls">Max number of balls to simulate.</param>
        public Pinball(int maxBalls)
        {
            this.maxBalls = maxBalls;

            boardBounds = new Rectangle(0, 0, 800, 600);

            random = new Random();

            balls = new List<Ball>();
            bumpers = new Dictionary<int, Bumper>();
        }

        /// <summary>Load the Pinball assets.</summary>
        public void LoadContent(ContentManager Content)
        {
            // Load textures
            ballTexture = Content.Load<Texture2D>("pinball/ball");
            bumperTexture = Content.Load<Texture2D>("pinball/bumper");
            bumperHit = Content.Load<SoundEffect>("sound/bounce");

            // Set widths
            ballWidth = ballTexture.Width * 0.9f;
            bumperWidth = bumperTexture.Width;
        }

        /// <summary>Adds and removes bumpers given the new state of the board.</summary>
        private void UpdateBumpers(Board board)
        {
            // Remove dead bumpers
            List<int> removedKeys = board.RemovedKeys();

            for (int i = 0; i < removedKeys.Count; i++)
            {
                // Bumper explode animation here
                bumpers.Remove(removedKeys[i]);
            }

            // Add new bumpers
            List<int> addedKeys = board.AddedKeys();

            for (int i = 0; i < addedKeys.Count; i++)
            {
                // Bumper add animation here
                bumpers.Add(addedKeys[i], new Bumper(board.GetCircle(addedKeys[i]), bumperWidth));
            }
        }

        /// <summary>Update the simulation.</summary>
        public void Update(GameTime gameTime, Board board)
        {

#if RELEASE_TIMER
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
#endif

            if (balls.Count < maxBalls)
            {
                // Make sure no balls are close to the spawn point
                bool canSpawn = true;

                for (int i = 0; i < balls.Count; i++)
                {
                    if (Vector2.Distance(Vector2.Zero, balls[i].Position) < 150f)
                    {
                        canSpawn = false;
                        break;
                    }
                }

                // If no balls were near, spawn
                if (canSpawn)
                {
                    balls.Add(new Ball(Vector2.Zero, ballWidth / 2, new Vector2(300)));
                }
            }

            // Update the bumpers
            UpdateBumpers(board);

            int stepsPerFrame = 25;
            float timeStep = (float)gameTime.ElapsedGameTime.TotalSeconds / stepsPerFrame;

            for (int step = 0; step < stepsPerFrame; step++)
            {
                // Update the balls
                for (int i = 0; i < balls.Count; i++)
                {
                    balls[i].Update(timeStep);

                    // Resolve collisions with the world
                    if (balls[i].Position.X < boardBounds.Left)
                    {
                        balls[i].VerticalCollision(boardBounds.Left);
                    }
                    if (balls[i].Position.X + ballWidth > boardBounds.Right)
                    {
                        balls[i].VerticalCollision(boardBounds.Right);
                    }
                    if (balls[i].Position.Y < boardBounds.Top)
                    {
                        balls[i].HorizontalCollision(boardBounds.Top);
                    }
                    if (balls[i].Position.Y + ballWidth > boardBounds.Bottom)
                    {
                        balls[i].HorizontalCollision(boardBounds.Bottom);
                    }

                    // Resolve collisions with other balls
                    for (int j = 0; j < balls.Count; j++)
                    {
                        // Don't test a ball against itself
                        if (i == j) { continue; }

                        // Check for ball to ball collision
                        if (Circle.Intersection(balls[i].Bounds, balls[j].Bounds))
                        {
                            balls[i].ElasticCollision(balls[j], timeStep);
                        }
                    }

                    // Resolve collisions with bumpers
                    foreach (Bumper bumper in bumpers.Values)
                    {
                        if (Circle.Intersection(balls[i].Bounds, bumper.Bounds))
                        {
                            // Resolve the balls motion
                            balls[i].BumperCollision(bumper, timeStep);

                            // Hit up the bumper
                            bumper.Hit();

                            // Only make a sound every 150ms
                            if (gameTime.TotalGameTime.TotalMilliseconds - lastHit > 150)
                            {
                                SoundEffectInstance sEI = bumperHit.CreateInstance();

                                sEI.Pitch = random.Next(-10, 10) * 0.01f;
                                sEI.Play();

                                lastHit = gameTime.TotalGameTime.TotalMilliseconds;
                            }
                        }
                    }
                }
            }
            
            // Update bumpers
            foreach (Bumper bumper in bumpers.Values)
            {
                bumper.Update();
            }

#if RELEASE_TIMER
            stopWatch.Stop();
            Console.WriteLine("Pinball.Update: {0}ms", stopWatch.Elapsed.TotalMilliseconds);
#endif
        }

        /// <summary>Draw the simulation to the screen.</summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            // Draw balls
            for (int i = 0; i < balls.Count; i++)
            {
                spriteBatch.Draw(ballTexture, balls[i].Position, Color.White);
            }

            // Draw bumpers
            foreach (Bumper bumper in bumpers.Values)
            {
                spriteBatch.Draw(bumperTexture, bumper.Position, null, Color.White, 0.0f, Vector2.Zero, bumper.Scale, SpriteEffects.None, 0);
            }
        }
    }
}
